package at.codejedi.sto.model.combat;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.GenericGenerator;

@Entity
public class Player implements IPlayer {
	@Entity
	@Table(name = "Player_Damage")
	public abstract class Damage implements IDamage {

		private double allDamage = 0;

		private double averageDamage;

		private List<IPlayerBattle> battles;

		private double damagePerSecond = 0;

		private double maxDamage;

		private String uuid;

		public Damage(List<IPlayerBattle> battles) {
			this.setBattles(battles);

		}

		@Override
		public double getAllDamage() {
			if (allDamage == 0) {
				double allNetDamageDealt = 0;
				for (IPlayerBattle battle : getBattles()) {
					IDamage damage = loadDamage(battle);
					allNetDamageDealt += damage.getAllDamage();
				}
				allDamage = allNetDamageDealt;
			}
			return allDamage;
		}

		@Override
		public double getAverageDamage() {
			if (averageDamage == 0) {
				double all = 0;
				for (IPlayerBattle battle : getBattles()) {
					IDamage damage = loadDamage(battle);
					all += damage.getAverageDamage();
				}
				averageDamage = all / getBattles().size();
			}
			return averageDamage;
		}

		@OneToMany(targetEntity = PlayerBattle.class)
		@Cascade(CascadeType.ALL)
		@JoinColumn(name = "playerBattles")
		public List<IPlayerBattle> getBattles() {
			return battles;
		}

		@Override
		public double getDamagePerSecond() {
			if (damagePerSecond == 0)
				damagePerSecond = loadNetDamagePerSecondDealt(getBattles());
			return damagePerSecond;
		}

		@Override
		public double getMaxDamage() {
			if (maxDamage == 0) {
				double max = 0;
				for (IPlayerBattle battle : getBattles()) {
					IDamage damage = loadDamage(battle);
					double damageBattle = damage.getMaxDamage();
					if (damageBattle > max)
						max = damageBattle;
				}
				maxDamage = max;
			}
			return maxDamage;
		}

		@Id
		@GeneratedValue(generator = "system-uuid")
		@GenericGenerator(name = "system-uuid", strategy = "uuid")
		@Column(name = "uuid", unique = true)
		public String getUuid() {
			return uuid;
		}

		public abstract IDamage loadDamage(IPlayerBattle playerBattle);

		private double loadNetDamagePerSecondDealt(List<IPlayerBattle> battles) {
			double allDamagePerSecondDealt = 0;
			for (IPlayerBattle battle : battles) {
				IDamage damage = loadDamage(battle);
				allDamagePerSecondDealt += damage.getDamagePerSecond();
			}
			return allDamagePerSecondDealt / battles.size();
		}

		@Override
		public void preLoad() {
			// TODO Auto-generated method stub

		}

		public void setAllDamage(double allDamage) {
			this.allDamage = allDamage;
		}

		public void setAverageDamage(double averageDamage) {
			this.averageDamage = averageDamage;
		}

		public void setBattles(List<IPlayerBattle> battles) {
			this.battles = battles;
		}

		public void setDamagePerSecond(double damagePerSecond) {
			this.damagePerSecond = damagePerSecond;
		}

		public void setMaxDamage(double maxDamage) {
			this.maxDamage = maxDamage;
		}

		public void setUuid(String uuid) {
			this.uuid = uuid;
		}
	}

	@Entity
	@Table(name = "Player_NetDamage")
	public class NetDamage extends Damage {

		private boolean received;

		public NetDamage(List<IPlayerBattle> battles, boolean received) {
			super(battles);
			// TODO Auto-generated constructor stub
			this.setReceived(received);
		}

		public boolean isReceived() {
			return received;
		}

		@Override
		public IDamage loadDamage(IPlayerBattle playerBattle) {
			if (isReceived())
				return playerBattle.getNetDamageReceived();
			else
				return playerBattle.getNetDamageDealt();
		}

		public void setReceived(boolean received) {
			this.received = received;
		}

	}

	@Entity
	@Table(name = "Player_RawDamage")
	public class RawDamage extends Damage {

		private boolean received;

		public RawDamage(List<IPlayerBattle> battles, boolean received) {
			super(battles);
			// TODO Auto-generated constructor stub
			this.setReceived(received);
		}

		public boolean isReceived() {
			return received;
		}

		@Override
		public IDamage loadDamage(IPlayerBattle playerBattle) {
			if (isReceived())
				return playerBattle.getRawDamageReceived();
			else
				return playerBattle.getRawDamageDealt();
		}

		public void setReceived(boolean received) {
			this.received = received;
		}

	}

	private static long loadBeginningMillis(List<IPlayerBattle> battles) {
		if (battles.size() == 0)
			throw new IllegalArgumentException("No Battles found!");
		IPlayerBattle first = battles.get(0);
		return first.getBeginningMillis();
	}

	private static long loadEndingMillis(List<IPlayerBattle> battles) {
		IPlayerBattle first = battles.get(battles.size() - 1);
		return first.getEndingMillis();
	}

	private static int loadSecondsPlayed(long beginningMillis, long endingMillis) {
		double diff = endingMillis - beginningMillis;
		return (int) diff / 1000;
	}

	private List<IPlayerBattle> battles;

	private long beginningMillis = 0;

	private long endingMillis = 0;

	private List<Entry> entries = new ArrayList<Entry>();

	private double logEntriesPerSecond;

	private IDamage netDamageDealt;

	private IDamage netDamageReceived;

	private String playerHandle;

	private String playerName;

	private IDamage rawDamageDealt;

	private IDamage rawDamageReceived;

	private int secondsPlayed = 0;

	private String uuid;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * at.codejedi.sto.model.combat.IPlayer#AddEntry(at.codejedi.sto.model.combat
	 * .Entry)
	 */
	@Override
	public void AddEntry(Entry e) {
		entries.add(e);
	}

	@Override
	@ManyToMany(targetEntity = PlayerBattle.class)
	@Cascade(CascadeType.ALL)
	@JoinColumn(name = "battles")
	public List<IPlayerBattle> getBattles() {
		if (battles == null)
			battles = loadBattles(entries, false);
		return battles;
	}

	@Override
	public long getBeginningMillis() {
		if (beginningMillis == 0)
			beginningMillis = loadBeginningMillis(battles);
		return beginningMillis;

	}

	@Override
	public long getEndingMillis() {
		if (endingMillis == 0)
			endingMillis = loadEndingMillis(battles);
		return endingMillis;

	}

	@Override
	@OneToMany(targetEntity = Entry.class)
	@Cascade(CascadeType.ALL)
	@JoinColumn(name = "allEntries")
	public List<Entry> getEntries() {
		return entries;
	}

	@Override
	public double getLogEntriesPerSecond() {
		if (logEntriesPerSecond == 0) {
			double all = 0;
			for (IPlayerBattle battle : battles) {
				all += battle.getLogEntriesPerSecond();
			}
			logEntriesPerSecond = all / battles.size();
		}
		return logEntriesPerSecond;
	}

	@Override
	@OneToOne(targetEntity = NetDamage.class)
	@Cascade(value = CascadeType.ALL)
	public IDamage getNetDamageDealt() {
		if (netDamageDealt == null)
			netDamageDealt = new NetDamage(getBattles(), false);
		return netDamageDealt;
	}

	@Override
	@OneToOne(targetEntity = NetDamage.class)
	@Cascade(value = CascadeType.ALL)
	public IDamage getNetDamageReceived() {
		if (netDamageReceived == null)
			netDamageReceived = new NetDamage(getBattles(), true);
		return netDamageReceived;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.codejedi.sto.model.combat.IPlayer#getPlayerHandle()
	 */
	@Override
	public String getPlayerHandle() {
		return playerHandle;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.codejedi.sto.model.combat.IPlayer#getPlayerName()
	 */
	@Override
	public String getPlayerName() {
		return playerName;
	}

	@Override
	@OneToOne(targetEntity = RawDamage.class)
	@Cascade(value = CascadeType.ALL)
	public IDamage getRawDamageDealt() {
		if (rawDamageDealt == null)
			rawDamageDealt = new RawDamage(getBattles(), false);
		return rawDamageDealt;
	}

	@Override
	@OneToOne(targetEntity = RawDamage.class)
	@Cascade(value = CascadeType.ALL)
	public IDamage getRawDamageReceived() {
		if (rawDamageReceived == null)
			rawDamageReceived = new RawDamage(getBattles(), true);
		return rawDamageReceived;
	}

	@Override
	public int getSecondsPlayed() {
		if (secondsPlayed == 0)
			secondsPlayed = loadSecondsPlayed(getBeginningMillis(),
					getEndingMillis());
		return secondsPlayed;

	}

	@Id
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	@Column(name = "uuid", unique = true)
	public String getUuid() {
		return uuid;
	}

	private List<IPlayerBattle> loadBattles(List<Entry> entries, boolean preload) {
		List<IPlayerBattle> battles = new ArrayList<IPlayerBattle>();

		PlayerBattle bt = null;
		long lastMillis = 0;
		List<Entry> lastEntries = null;
		for (Entry entry : entries) {
			long myMillis = entry.getTime().getTimeInMillis();
			if (bt != null) {
				long diff = myMillis - lastMillis;
				if (diff < 60000) {
					lastMillis = myMillis;
					lastEntries.add(entry);
					continue;
				}
				bt.setEndingMillis(lastMillis);
				battles.add(bt);
				lastEntries = null;
				if (preload)
					bt.preload();
				bt = null;
			}

			if (bt == null) {
				bt = new PlayerBattle();
				bt.setPlayer(this);
				lastEntries = new ArrayList<Entry>();
				lastEntries.add(entry);
				bt.setEntries(lastEntries);
				bt.setBeginningMillis(myMillis);
				lastMillis = myMillis;
			}
		}
		if (bt != null)
			battles.add(bt);

		return battles;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.codejedi.sto.model.combat.IPlayer#preLoad()
	 */
	@Override
	public void preLoad() {
		battles = loadBattles(entries, true);
		if (battles.size() == 0)
			return;

		beginningMillis = loadBeginningMillis(battles);
		endingMillis = loadEndingMillis(battles);
		secondsPlayed = loadSecondsPlayed(getBeginningMillis(),
				getEndingMillis());
	}

	public void setBattles(List<IPlayerBattle> battles) {
		this.battles = battles;
	}

	public void setBeginningMillis(long beginningMillis) {
		this.beginningMillis = beginningMillis;
	}

	public void setEndingMillis(long endingMillis) {
		this.endingMillis = endingMillis;
	}

	public void setEntries(List<Entry> entries) {
		this.entries = entries;
	}

	public void setLogEntriesPerSecond(double logEntriesPerSecond) {
		this.logEntriesPerSecond = logEntriesPerSecond;
	}

	public void setNetDamageDealt(IDamage netDamageDealt) {
		this.netDamageDealt = netDamageDealt;
	}

	public void setNetDamageReceived(IDamage netDamageReceived) {
		this.netDamageReceived = netDamageReceived;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * at.codejedi.sto.model.combat.IPlayer#setPlayerHandle(java.lang.String)
	 */
	@Override
	public void setPlayerHandle(String playerHandle) {
		this.playerHandle = playerHandle;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see at.codejedi.sto.model.combat.IPlayer#setPlayerName(java.lang.String)
	 */
	@Override
	public void setPlayerName(String playerName) {
		this.playerName = playerName;
	}

	public void setRawDamageDealt(IDamage rawDamageDealt) {
		this.rawDamageDealt = rawDamageDealt;
	}

	public void setRawDamageReceived(IDamage rawDamageReceived) {
		this.rawDamageReceived = rawDamageReceived;
	}

	public void setSecondsPlayed(int secondsPlayed) {
		this.secondsPlayed = secondsPlayed;
	}

	public void setUuid(String uuid) {
		this.uuid = uuid;
	}

	@Override
	public String toCommandLineRepresentation() {
		StringBuilder value = new StringBuilder();
		value.append("PlayerName:");
		value.append(getPlayerName());
		value.append("\n");

		for (IBattleStatistic battle : getBattles()) {
			value.append(battle);
			value.append("\n");
		}
		return value.toString();
	}

	@Override
	public String toString() {
		return getPlayerName();
	}
}
